package com.hsae.platform.upgrade

import android.app.DownloadManager
import android.app.DownloadManager.Query
import android.app.DownloadManager.Request
import android.content.*
import android.content.pm.PackageManager
import android.net.Uri
import android.os.Build.VERSION.SDK_INT
import android.os.Build.VERSION_CODES.N
import android.os.Build.VERSION_CODES.O
import android.os.Bundle
import android.os.Handler
import android.os.Looper
import android.provider.Settings
import android.util.Log
import androidx.annotation.RequiresApi
import androidx.appcompat.app.AlertDialog
import androidx.core.content.FileProvider
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentActivity
import androidx.lifecycle.lifecycleScope
import kotlinx.coroutines.*
import java.io.File

/**
 * @author zhuyouyong <zhuyouyong@hangsheng.com.cn>
 * Created on 2020/11/13.
 */
class UpgradeFrag : Fragment() {

    companion object {
        private const val downloadManagerName = "com.android.providers.downloads"
        private val logTag = UpgradeFrag::class.java.simpleName

        const val keyCode = "code"
        const val keyVersion = "version"
        const val keySize = "size"
        const val keyLog = "log"
        const val keyUrl = "url"
        const val keyTheme = "theme"
        const val keyForceUseDownloadManager = "forceUseDownloadManager"

        const val illegalDownloadId = -1L
        const val downloadDir = "download"
        const val downloadName = "0.apk"

        fun show(fragAct: FragmentActivity, args: Bundle) {
            val tag = "upgrade_frag"
            fragAct.supportFragmentManager.findFragmentByTag(tag)?.let {
                Log.e(logTag,"$logTag was already shown.")
            } ?: fragAct.runOnUiThread {
                fragAct.supportFragmentManager
                    .beginTransaction()
                    .add(UpgradeFrag().apply { arguments = args }, tag)
                    .commitAllowingStateLoss()
            }
        }

        fun deleteDownloadFile(context: Context) {
            val file = File(context.getExternalFilesDir(downloadDir), downloadName)
            if (file.exists())
                runCatching { file.delete() }
        }
        
        fun downloadFileExists(context: Context) =
            File(context.getExternalFilesDir(downloadDir), downloadName).exists()
        
        fun isDownloadEnabled(context: Context) =
            context.packageManager.getApplicationEnabledSetting(downloadManagerName).let {
                it == PackageManager.COMPONENT_ENABLED_STATE_DEFAULT ||
                        it == PackageManager.COMPONENT_ENABLED_STATE_ENABLED
            }

        fun openDownloadSetting(context: Context) {
            try {
                context.startActivity(Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS).setData(
                    Uri.parse("package:$downloadManagerName")
                ))
            } catch (e: Exception) {
                context.startActivity(Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS))
            }
        }
    }

    private var code = 0
    private lateinit var version: String
    private lateinit var size: String
    private lateinit var log: String
    private lateinit var url: String
    private var theme = 0
    private var forceUseDownloadManager = true
    private lateinit var dlg: AlertDialog
    private lateinit var dlgNegativeClick: DlgClickWrapper
    private var dlgPositiveClick: DlgClickWrapper? = null
    private var viewPositiveClick: ViewClickWrapper? = null
    private var downloadManager: DownloadManager? = null
    private var downloadId = illegalDownloadId
    private val query by lazy { Query().setFilterById(downloadId) }
    private lateinit var sp: SharedPreferences
    private var downloadedCode = 0

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        init()
        showHint()
    }

    override fun onDestroy() {
        super.onDestroy()
        unregisterDownloadReceiver()
        if (downloadId != illegalDownloadId) downloadManager?.remove(downloadId)
        if (dlg.isShowing) dlg.dismiss()
        dlgNegativeClick.release()
        dlgPositiveClick?.release()
        viewPositiveClick?.release()
        enableDownloadClick?.release()
        Log.i(logTag,"$logTag onDestroy.")
    }

    private fun init() {
        requireArguments().apply {
            code = getInt(keyCode)
            version = getString(keyVersion)!!
            size = getString(keySize)!!
            log = getString(keyLog)!!
            url = getString(keyUrl)!!
            theme = getInt(keyTheme)
            forceUseDownloadManager = getBoolean(keyForceUseDownloadManager, true)
        }
        sp = requireContext().getSharedPreferences("upgrade", Context.MODE_PRIVATE)
        downloadedCode = sp.getInt(keyCode, 0)
        dlgNegativeClick = DlgClickWrapper {_,_-> destroy()}
    }

    private fun destroy() {
        Handler(Looper.getMainLooper()).post {
            parentFragmentManager
                .beginTransaction()
                .remove(this@UpgradeFrag)
                .commitAllowingStateLoss()
        }
    }

    private fun showHint() {
        downloadManager = requireContext().getSystemService(Context.DOWNLOAD_SERVICE) as DownloadManager?
        downloadManager?.let {
            when {
                isDownloadEnabled(requireContext()) -> showDownloadHint()
                forceUseDownloadManager -> showEnableDownloadHint()
                else -> showBrowserHint()
            }
        } ?: showBrowserHint()
    }

    private var requestEnableDownload = false
    private var enableDownloadClick: DlgClickWrapper? = null
    private fun showEnableDownloadHint() {
        enableDownloadClick = DlgClickWrapper {_, _ ->
            requestEnableDownload = true
            openDownloadSetting(requireContext())
        }
        val builder =
            if (theme != 0) AlertDialog.Builder(requireContext(), theme)
            else AlertDialog.Builder(requireContext())
        dlg = builder
            .setTitle("下载管理器")
            .setMessage("下载管理器被禁用，请启用")
            .setPositiveButton("确定", enableDownloadClick)
            .setNegativeButton("取消", dlgNegativeClick)
            .setCancelable(false).show()
    }

    private fun showBrowserHint() {
        dlgPositiveClick = DlgClickWrapper {_, _ ->
            if (alreadyDownloaded()) {
                launchInstall()
            } else {
                startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(url)))
                destroy()
            }
        }
        val builder =
            if (theme != 0) AlertDialog.Builder(requireContext(), theme)
            else AlertDialog.Builder(requireContext())
        dlg = builder
                .setTitle("发现新版本：V$version")
                .setMessage("文件大小：$size\n更新日志：\n$log")
                .setPositiveButton("升级", dlgPositiveClick)
                .setNegativeButton("取消", dlgNegativeClick)
                .setCancelable(false).show()
    }

    private fun showDownloadHint() {
        val builder =
            if (theme != 0) AlertDialog.Builder(requireContext(), theme)
            else AlertDialog.Builder(requireContext())
        dlg = builder
            .setTitle("发现新版本：V$version")
            .setMessage("文件大小：$size\n更新日志：\n$log")
            .setPositiveButton("升级", null)
            .setNegativeButton("取消", dlgNegativeClick)
            .setCancelable(false).show()
        viewPositiveClick = ViewClickWrapper {
            it.isEnabled = false
            if (alreadyDownloaded()) {
                launchInstall()
            } else {
                lifecycleScope.launch {
                    withContext(Dispatchers.IO) {
                        sp.edit().clear().apply()
                        deleteDownloadFile(requireContext())
                        Log.i(logTag,"deleteDownloadFile")
                    }
                    Log.i(logTag,"start to upgrade")
                    upgrade()
                }
            }
        }
        dlg.getButton(AlertDialog.BUTTON_POSITIVE).setOnClickListener(viewPositiveClick)
    }

    private fun upgrade() {
        downloadId = downloadManager!!.enqueue(
                Request(Uri.parse(url))
                        .setNotificationVisibility(Request.VISIBILITY_HIDDEN)
                        .setDestinationInExternalFilesDir(
                                requireContext(),
                                downloadDir,
                                downloadName
                        )
        )
        registerDownloadReceiver()
        queryProgress()
    }

    @Volatile private var cancelQuery = false
    private fun queryProgress() {
        lifecycleScope.launch {
            while (isActive && !cancelQuery) {
                val percent = withContext(Dispatchers.IO) {
                    downloadManager!!.query(query).run {
                        moveToFirst()
                        val soFar = getLong(getColumnIndex(DownloadManager.COLUMN_BYTES_DOWNLOADED_SO_FAR))
                        val total = getLong(getColumnIndex(DownloadManager.COLUMN_TOTAL_SIZE_BYTES))
                        close()
                        (soFar * 100F / total).toInt()
                    }
                }
                Log.i(logTag,"percent: $percent")
                if (dlg.isShowing) dlg.setMessage("下载进度 -> $percent / 100")
                delay(2000L)
            }
        }
    }
    
    private fun alreadyDownloaded() = code == downloadedCode && downloadFileExists(requireContext())
    
    private fun launchInstall() {
        if (hasInstallPermission()) install()
        else requestInstallPermission()
    }

    private val downloadReceiver by lazy {
        object : BroadcastReceiver() {
            override fun onReceive(context: Context?, intent: Intent?) {
                intent?.let {
                    val extraId = it.getLongExtra(DownloadManager.EXTRA_DOWNLOAD_ID, -1L)
                    if (downloadId == extraId) {
                        downloadId = illegalDownloadId
                        cancelQuery = true
                        sp.edit().putInt(keyCode, code).apply()
                        unregisterDownloadReceiver()
                        launchInstall()
                    }
                }
            }
        }
    }

    private fun hasInstallPermission() =
        SDK_INT < O || requireContext().packageManager.canRequestPackageInstalls()

    private var requestInstallPermission = false
    @RequiresApi(O)
    private fun requestInstallPermission() {
        requestInstallPermission = true
        startActivity(
            Intent(Settings.ACTION_MANAGE_UNKNOWN_APP_SOURCES)
                .setData(Uri.parse("package:${requireContext().packageName}"))
        )
    }

    private fun install() {
        val type = "application/vnd.android.package-archive"
        val file = File(requireContext().getExternalFilesDir(downloadDir), downloadName)
        val install = Intent(Intent.ACTION_VIEW).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
        if (SDK_INT >= N) {
            install.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
            install.setDataAndType(
                FileProvider.getUriForFile(
                    requireContext(),
                    "${requireContext().packageName}.upgrade.provider",
                    file
                ), type
            )
        } else {
            install.setDataAndType(Uri.fromFile(file), type)
        }
        startActivity(install)
        destroy()
    }

    override fun onResume() {
        super.onResume()
        when {
            requestInstallPermission -> {
                requestInstallPermission = false
                if (hasInstallPermission()) {
                    install()
                } else {
                    destroy()
                }
            }
            requestEnableDownload -> {
                requestEnableDownload = false
                if (isDownloadEnabled(requireContext())) {
                    showDownloadHint()
                } else {
                    destroy()
                }
            }
        }
    }

    private var registerDownloadReceiver = false
    private fun registerDownloadReceiver() {
        if (!registerDownloadReceiver) {
            registerDownloadReceiver = true
            requireContext().registerReceiver(
                downloadReceiver, IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE)
            )
        }
    }

    private fun unregisterDownloadReceiver() {
        if (registerDownloadReceiver) {
            registerDownloadReceiver = false
            requireContext().unregisterReceiver(downloadReceiver)
        }
    }
}